# Подключение к клиенту мессенждера
from telethon.sync import TelegramClient
from telethon.types import PeerChannel

# Импорты знакомых библиотек
import random, pickle, asyncio, os
from datetime import timezone, timedelta, datetime
from datetimerange import DateTimeRange

# Импорты локальный файлов
import parse_appinfo
import do_bigramms as bg
from instruments import apply_timeout, throw_dice

data = parse_appinfo.parse_data()
login_data = data['login']
settings_data = data['settings']

# Подключение к некоторому клиенту.
# TODO: всегда просит совершить вход, стоит исправить этот код так, чтобы использовать данные из appinfo для входа
client = TelegramClient(str(login_data['log_as']), int(login_data['api_id']), login_data['api_hash'], system_version="4.16.30-vxCUSTOM")


""" УПРАВЛЕНИЕ ВРЕМЕНЕМ """

def form_timerange():
    """ Из данных appinfo формируем объект DateTimeRange, который обозначает время таймаута."""
    today = form_now()
    today = today.replace(hour=settings_data["timeout_begin"], minute=0, second=0, microsecond=0)
    new_t = today + timedelta(hours=settings_data["timeout_length"])
    # Составить тест datetimerange и сравнить, если текущее время в этом диапазоне, то оставлять его. Иначе прибавить +1 день
    if form_now() not in DateTimeRange(today, new_t):
        try:
            today = today.replace(day = today.day + 1)
            new_t = new_t.replace(day = new_t.day + 1)
        except ValueError:
            today = today.replace(month = today.month + 1, day = 1)
            new_t = new_t.replace(month = new_t.month + 1, day = 1)


    if not os.path.exists(settings_data['timeout_filename']):
        with open(settings_data['timeout_filename'], 'wb') as f:
            pickle.dump(today.strftime("%d.%m.%Y %H:%M:%S") + '|' + new_t.strftime("%d.%m.%Y %H:%M:%S"), f)
    else:
        with open(settings_data['timeout_filename'], 'rb') as tm:
            l, r = pickle.load(tm).split('|')
            lTime = datetime.strptime(l, "%d.%m.%Y %H:%M:%S")
            rTime = datetime.strptime(r, "%d.%m.%Y %H:%M:%S")

        if form_now().hour >= rTime.hour:
            with open(settings_data['timeout_filename'], 'wb') as tm:
                pickle.dump(today.strftime("%d.%m.%Y %H:%M:%S") + '|' + new_t.strftime("%d.%m.%Y %H:%M:%S"), tm)
        
        return DateTimeRange(lTime, rTime)
    
    return DateTimeRange(today, new_t)

def form_offset(offset):
    return form_now() + timedelta(minutes=offset)

def form_now():
    return (datetime.now(timezone.utc) + timedelta(hours=settings_data["time_offset"])).replace(tzinfo=None)

"""
Код из parse_messages.py
Так как там также используется client, telethon ругается из-за наличия двух потоков обращения к клиенту.
Поэтому я объединил данные файлы, чтобы все обращения были единичными. Когда будет найден способ всё исправить, можно сделать обратное деление файлов.
"""
async def save():
    """ Взять сообщения из некоторого канала и составить биграммную модель """

    offset = settings_data['begin_offset']

    # ID сообщений что уже запаршены. Полезно, так как будем прыгать от сообщения к сообщению.
    already_parsed = []
    parsed_messages = []
    
    # Если имеется уже заранее готовое сообщение, то стартуем уже с него
    if os.path.exists(settings_data['last_message_filename']):
        # Стоит ли эта проверка, если есть with?..
        with open(settings_data['last_message_filename'], 'rb') as lm:
            try:
                _of = pickle.load(lm)
                offset = max(_of, offset)
            except EOFError:
                pass

    print(f"\nStaring to parce at {form_now()} from message {offset}.")

    last_parsed = offset
    async for message in client.iter_messages(settings_data['chat_id'], reverse=True, offset_id=offset-1):
        # Скипаем ненужные сообщения:
        if message.from_id.__class__ == PeerChannel or (message.message is not None and (message.message.startswith('/') or message.message.startswith('https://'))) or message.message == '' or message.message is None:
            last_parsed = message.id
            continue

        # TODO: Проверка на контекст.
        # 1. Если сообщение А является ответом на сообщение Б, значит сообщение А в контексте сообщения Б.
        # 2. Если сообщение А написано в пределах 15 секунд после сообщения Б, значит сообщение А в контексте сообщения Б.
        # 3. Если сообщение А написано в пределах 30 секунд после сообщения Б от ЭТОГО ЖЕ пользователя, значит сообщение А в контексте сообщения Б.
        # Сообщения в контексте мы сохраняем как отдельный текст, и потом его разбиваем на биграммы.

        parsed_messages.append(message.message)

    # Сохраним последний id сообщения в файл
    with open(settings_data['last_message_filename'], 'wb') as lm:
        pickle.dump(last_parsed, lm)

    with open(settings_data['last_message_filename']+'BKP', 'wb') as lm:
        pickle.dump(last_parsed, lm)

    # Если сообщений нет, то нам сохранять нечего, просто выходим из функии
    if len(parsed_messages) == 0:
        print("* Trying to parce: no messages.")
        return

    model = bg.do_model(bg.prepare_text(parsed_messages))

    # Если модель уже есть, просто оттуда берём всю информацию
    if os.path.exists(settings_data['model_filename']):
        with open(settings_data['model_filename'], 'rb') as mf:
            model_new = model
            model = pickle.load(mf)
        
        for item, value in model_new.items():
            model[item] = value
    
    # Сохраняем модель
    with open(settings_data['model_filename'], 'wb') as mf:
        pickle.dump(model, mf)


async def write():
    """ Сгенерировать сообщение используя биграммную модель и отправить его в чат SENDER_ID"""
    with open(settings_data['model_filename'], 'rb') as f:
        _dict = pickle.load(f)
    
    randseq = random.choice(list(_dict.items()))

    text = bg.do_text(_dict, randseq[0])
    await client.send_message(settings_data['sender_id'], text)


async def post_to_channel():
    """ Основной цикл программы. Парсинг и пост сообщения через случайный промежуток времени."""
    while True:
        await client.start()
        await save()
        await write()
        await client.disconnect()
        # Логгинг в файл? -> Имеется docker logs
        print(f"Printed message at {form_now()}")
        
        time_to_sleep = throw_dice.throw_dice(settings_data['time_dice_count'], settings_data['time_dice_type'])
        time_to_sleep = apply_timeout.apply_timeout(time_to_sleep, settings_data['timeout_multiplier'], form_timerange(), settings_data['time_offset'])
        print(f"Going sleep til {form_offset(time_to_sleep)} ({time_to_sleep} minutes)\n***********************\n")
        await asyncio.sleep(60*time_to_sleep)


if __name__ == '__main__':
    asyncio.run(post_to_channel())